﻿using NUnit.Framework;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using DotNetCommon.Extensions;
using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Numerics;
using System.Drawing;
using System.Text.Json.Nodes;
using System.Text.Json;

namespace DotNetCommon.Test.Extensions
{
    [TestFixture]
    public class ObjectTests_DeepClone
    {
        [Test]
        public void Test_null()
        {
            object obj = null;
            var objNew = obj.DeepClone();
            objNew.ShouldBeNull();
        }

        [Test]
        public void Test_JObjectJArray()
        {
            var obj = new { Id = 1, Name = "小明" }.ToJson().ToObject<JObject>() as object;
            var obj2 = obj.DeepClone();
            Assert.IsFalse(obj2 == obj);

            var arr = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject<JArray>() as object;
            obj2 = arr.DeepClone();
            Assert.IsFalse(obj2 == arr);
        }

        [Test]
        public void Test_JsonObjectJsonArrayJsonDocument()
        {
            var obj = new { Id = 1, Name = "小明" }.ToJson().ToObject<JsonObject>() as object;
            var obj2 = obj.DeepClone();
            Assert.IsFalse(obj2 == obj);

            var arr = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject<JsonArray>() as object;
            obj2 = arr.DeepClone();
            Assert.IsFalse(obj2 == arr);

            var doc = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject<JsonDocument>() as object;
            obj2 = doc.DeepClone();
            Assert.IsFalse(obj2 == arr);
        }

        [Test]
        public void Test_CloneSimple()
        {
            bool var_bool = true;
            var new_bool = var_bool.DeepClone();
            new_bool.ShouldBe(true);

            ThreadState var_enum = ThreadState.Terminated;
            var new_enum = var_enum.DeepClone();
            new_enum.ShouldBe(ThreadState.Terminated);

            char var_char = 'X';
            var new_char = var_char.DeepClone();
            new_char.ShouldBe('X');

            byte var_byte = 1;
            var new_byte = var_byte.DeepClone();
            new_byte.ShouldBe((byte)1);

            sbyte var_sbyte = 1;
            var new_sbyte = var_sbyte.DeepClone();
            new_sbyte.ShouldBe((sbyte)1);

            short var_short = 1;
            var new_short = var_short.DeepClone();
            new_short.ShouldBe((short)1);

            ushort var_ushort = 1;
            var new_ushort = var_ushort.DeepClone();
            new_ushort.ShouldBe((ushort)1);

            int var_int = 1;
            var new_int = var_int.DeepClone();
            new_int.ShouldBe(1);

            uint var_uint = 1;
            var new_uint = var_uint.DeepClone();
            new_uint.ShouldBe((uint)1);

            long var_long = 1;
            var new_long = var_long.DeepClone();
            new_long.ShouldBe(1);

            ulong var_ulong = 1;
            var new_ulong = var_ulong.DeepClone();
            new_ulong.ShouldBe((ulong)1);

            float var_float = 1;
            var new_float = var_float.DeepClone();
            new_float.ShouldBe((float)1);

            double var_double = 1;
            var new_double = var_double.DeepClone();
            new_double.ShouldBe((double)1);

            decimal var_decimal = 1;
            var new_decimal = var_decimal.DeepClone();
            new_decimal.ShouldBe((decimal)1);

            Guid var_guid = Guid.Parse("9ec99244-a71b-4fab-b933-639bfa90dddf");
            var new_guid = var_guid.DeepClone();
            new_guid.ShouldBe(var_guid);

            DateTime var_dt = DateTime.Parse("2022-01-01");
            var new_dt = var_dt.DeepClone();
            new_dt.ShouldBe(var_dt);

            DateTimeOffset var_dtoff = DateTimeOffset.Parse("2022-01-01 01:01:01 +08:00");
            var new_dtoff = var_dtoff.DeepClone();
            new_dtoff.ShouldBe(var_dtoff);

            DBNull var_dbnull = DBNull.Value;
            var new_dbnull = var_dbnull.DeepClone();
            new_dbnull.ShouldBe(var_dbnull);

            string var_string = "123";
            var new_string = var_string.DeepClone();
            new_string.ShouldBe(var_string);

            DateOnly var_dtonly = DateOnly.Parse("2022-01-01");
            var new_dtonly = var_dtonly.DeepClone();
            new_dtonly.ShouldBe(var_dtonly);

            TimeOnly var_tmonly = TimeOnly.Parse("01:01:01");
            var new_tmonly = var_tmonly.DeepClone();
            new_tmonly.ShouldBe(var_tmonly);


            //Vector
            Vector2 v2 = new Vector2(1, 2);
            var newV2 = v2.DeepClone();
            newV2.X = 5;
            Assert.IsTrue(v2.X == 1);
            Assert.IsTrue(newV2.X == 5);

            Vector3 v3 = new Vector3(1, 2, 3);
            var newV3 = v3.DeepClone();
            newV3.X = 5;
            Assert.IsTrue(v3.X == 1);
            Assert.IsTrue(newV3.X == 5);

            Vector4 v4 = new Vector4(1, 2, 3, 4);
            var newV4 = v4.DeepClone();
            newV4.X = 5;
            Assert.IsTrue(v4.X == 1);
            Assert.IsTrue(newV4.X == 5);


            Matrix3x2 matrix3X2 = new Matrix3x2(1, 2, 3, 4, 5, 6);
            var newMatrix3X2 = matrix3X2.DeepClone();
            newMatrix3X2.M11 = 10;
            Assert.IsTrue(matrix3X2.M11 == 1);
            Assert.IsTrue(newMatrix3X2.M11 == 10);

            Matrix4x4 matrix4x4 = new Matrix4x4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
            var newMatrix4x4 = matrix4x4.DeepClone();
            newMatrix4x4.M11 = 10;
            Assert.IsTrue(matrix4x4.M11 == 1);
            Assert.IsTrue(newMatrix4x4.M11 == 10);

            Plane plane = new Plane(1, 2, 3, 4);
            var newPlane = plane.DeepClone();
            newPlane.Normal.X = 9;
            Assert.IsTrue(plane.Normal.X == 1);
            Assert.IsTrue(newPlane.Normal.X == 9);

            Quaternion quaternion = new Quaternion(1, 2, 3, 4);
            var newQuaternion = quaternion.DeepClone();
            newQuaternion.X = 9;
            Assert.IsTrue(quaternion.X == 1);
            Assert.IsTrue(newQuaternion.X == 9);

            //other
            Color color = Color.FromArgb(1, 1, 1, 1);
            var newColor = color.DeepClone();
            Assert.IsTrue(color == newColor);

            Point point = new Point(1, 2);
            var newPoint = point.DeepClone();
            newPoint.X = 9;
            Assert.IsTrue(point.X == 1);
            Assert.IsTrue(newPoint.X == 9);

            RectangleF rectangle = new RectangleF(1, 1, 1, 1);
            var newRectangle = rectangle.DeepClone();
            newRectangle.X = 9;
            Assert.IsTrue(rectangle.X == 1);
            Assert.IsTrue(newRectangle.X == 9);
        }

        [Test]
        public void Test_CloneSimpleList()
        {
            //Vector
            var list2 = new List<Vector2>
            {
                new Vector2(1, 2),
                new Vector2(1, 20),
            };
            var newList2 = list2.DeepClone();
            newList2.ToJson().ShouldBe("[{\"X\":1,\"Y\":2},{\"X\":1,\"Y\":20}]");

            //Vector2d
            var list = new List<Vector2d>
            {
                new Vector2d(1, 2),
                new Vector2d(1, 20),
            };
            var newList = list.DeepClone();
            newList.ToJson().ShouldBe("[{\"X\":1,\"Y\":2},{\"X\":1,\"Y\":20}]");
        }

        public struct Vector2d
        {
            public double X { get; set; }
            public double Y { get; set; }
            public Vector2d(double x, double y)
            {
                this.X = x;
                this.Y = y;
            }
        }

        #region Poco
        [Test]
        public void Test_ClonePoco()
        {
            var poco = new Poco
            {
                Id = 1,
                Name = "小明",
                Birth = DateTime.Parse("2022-01-02"),
                Field = "Field",
                FuProp = 3,
                FuField = "FuField",
                V2 = new Vector2(1, 2),
                V3 = new Vector3(1, 2, 3),
                intNullArray2_2 = new int?[][] { new int?[] { 1, null }, new int?[] { 2, 3 } }
            };
            var newPoco = poco.DeepClone();
            poco.ShouldNotBe(newPoco);
            Assert.IsTrue(newPoco.intNullArray2_2[0][0] == 1);
            Assert.IsTrue(newPoco.intNullArray2_2[0][1] == null);
            newPoco.Id = 2;
            newPoco.ToString().ShouldBe("id=2,name=小明,birth=2022-01-02");
            poco.ToString().ShouldBe("id=1,name=小明,birth=2022-01-02");

            newPoco.intNullArray2_2[1][1] = null;
            Assert.IsTrue(poco.intNullArray2_2[1][1] == 3);
        }

        class PocoFu
        {
            public int FuProp { get; set; }
            public string FuField;
        }

        class Poco : PocoFu
        {
            public int? intNull { get; set; }
            public int?[] intNullArray { get; set; }
            public int?[][] intNullArray2 { get; set; }
            public int?[][] intNullArray2_2 { get; set; }
            public Vector2? Vector2Null { get; set; }
            public Vector2?[] Vector2NullArray { get; set; }
            public List<Vector2?> Vector2NullList { get; set; }
            public Vector2 V2 { get; set; }
            public Vector3 V3 { get; set; }
            public string Field;
            public int Id { get; set; }
            public string Name { get; set; }
            public DateTime Birth { get; set; }
            public override string ToString()
            {
                return $"id={Id},name={Name},birth={Birth.ToString("yyyy-MM-dd")}";
            }
        }
        #endregion

        [Test]
        public void Test_CloneArray()
        {
            var arr = new int[] { 1, 2 };
            var newArr = arr.DeepClone();
            Assert.IsTrue(newArr != arr);
            newArr[0] = 2;
            arr.ToStringSeparated(",").ShouldBe("1,2");
            newArr.ToStringSeparated(",").ShouldBe("2,2");
        }

        [Test]
        public void Test_CloneIEnumerable()
        {
            IEnumerable<int> ienumerable = demo();
            var newienumerable = ienumerable.DeepClone();
            Assert.IsTrue(newienumerable != ienumerable);
            newienumerable.ToStringSeparated(",").ShouldBe("1,2");

            IEnumerable<int> demo()
            {
                yield return 1;
                yield return 2;
            }
        }

        [Test]
        public void Test_CloneList()
        {
            List<int> list = new List<int> { 1, 2, 3 };
            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);
            newList.Add(1);
            newList.ToStringSeparated(",").ShouldBe("1,2,3,1");
            list.ToStringSeparated(",").ShouldBe("1,2,3");
        }

        [Test]
        public void Test_HashSet()
        {
            HashSet<int> set = new HashSet<int> { 1, 2, 3 };
            var newSet = set.DeepClone();
            Assert.IsTrue(newSet != set);
            newSet.Add(4);
            newSet.ToStringSeparated(",").ShouldBe("1,2,3,4");
            set.ToStringSeparated(",").ShouldBe("1,2,3");
        }

        [Test]
        public void Test_Dictionary()
        {
            Dictionary<int, string> dic = new Dictionary<int, string>
            {
                {1,"1" },
                {2,"2" },
                {3,"3" }
            };
            var newDic = dic.DeepClone();
            Assert.IsTrue(newDic != dic);
            newDic.Add(4, "4");
            newDic.Keys.ToStringSeparated(",").ShouldBe("1,2,3,4");
            dic.Keys.ToStringSeparated(",").ShouldBe("1,2,3");
        }

        [Test]
        public void Test_ValueTuple()
        {
            (int, string) tuple = (1, "小明");
            var newTuple = tuple.DeepClone();
            Assert.IsTrue(newTuple == tuple);
            newTuple.Item2 = "小花";
            tuple.Item2.ShouldBe("小明");

            //超长
            (string name1, string name2, string name3, string name4, string name5, string name6, string name7,
                string name8, string name9, string name10, string name11, string name12, string name13, string name14,
                string name15, string name16, string name17, string name18, string name19, string name20, string name21,
                string name22, string name23, string name24, string name25, string name26, string name27, string name28,
                string name29, string name30) tuple2 = (
                "name1", "name2", "name3", "name4", "name5", "name6", "name7",
"name8", "name9", "name10", "name11", "name12", "name13", "name14",
"name15", "name16", "name17", "name18", "name19", "name20", "name21",
"name22", "name23", "name24", "name25", "name26", "name27", "name28",
"name29", "name30");
            var newTuple2 = tuple2.DeepClone();
            Assert.IsTrue(newTuple2 == tuple2);
            newTuple2.name1.ShouldBe("name1");
            newTuple2.name2.ShouldBe("name2");
            newTuple2.name29.ShouldBe("name29");
            newTuple2.name30.ShouldBe("name30");
        }

        [Test]
        public void Test_LinkedList()
        {
            LinkedList<int> list = new LinkedList<int>();
            list.AddLast(1);
            list.AddLast(2);
            list.AddLast(3);

            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);
            newList.AddLast(4);
            newList.ToStringSeparated(",").ShouldBe("1,2,3,4");
            list.ToStringSeparated(",").ShouldBe("1,2,3");
        }

        [Test]
        public void Test_Tuple()
        {
            Tuple<int, string> tuple = new Tuple<int, string>(1, "小明");

            var newTuple = tuple.DeepClone();
            Assert.IsFalse(newTuple == tuple);
            newTuple.Item1.ShouldBe(1);
            newTuple.Item2.ShouldBe("小明");

            //超长
            var tuple2 = new Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string>>>>>("name1", "name2", "name3", "name4", "name5", "name6", "name7", new Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string>>>>("name8", "name9", "name10", "name11", "name12", "name13", "name14", new Tuple<string, string, string, string, string, string, string, Tuple<string, string, string, string, string, string, string, Tuple<string, string>>>("name15", "name16", "name17", "name18", "name19", "name20", "name21", new Tuple<string, string, string, string, string, string, string, Tuple<string, string>>("name22", "name23", "name24", "name25", "name26", "name27", "name28", new Tuple<string, string>("name29", "name30")))));

            var newTuple2 = tuple2.DeepClone();
            Assert.IsFalse(newTuple2 == tuple2);
            newTuple2.Item1.ShouldBe("name1");
            newTuple2.Rest.Item1.ShouldBe("name8");
            newTuple2.Rest.Rest.Item1.ShouldBe("name15");
            newTuple2.Rest.Rest.Rest.Item1.ShouldBe("name22");
            newTuple2.Rest.Rest.Rest.Rest.Item1.ShouldBe("name29");
            newTuple2.Rest.Rest.Rest.Rest.Item2.ShouldBe("name30");
        }

        [Test]
        public void Test_ReadOnlyCollection()
        {
            var list = new List<int> { 1, 2 };
            ReadOnlyCollection<int> readList = new ReadOnlyCollection<int>(list);
            var newReadList = readList.DeepClone();

            Assert.IsTrue(newReadList != readList);
            list.Add(3);
            readList.ToStringSeparated(",").ShouldBe("1,2,3");
            newReadList.ToStringSeparated(",").ShouldBe("1,2");
        }

        #region CloneStruct
        [Test]
        public void Test_Struct()
        {
            var stru = new MyStruct { Id = 1, Name = "小明" };
            var newStru = stru.DeepClone();

            newStru.ToString().ShouldBe("id=1,name=小明");
        }

        struct MyStruct
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public override string ToString()
            {
                return $"id={Id},name={Name}";
            }
        }
        #endregion

        [Test]
        public void Test_Anonymous()
        {
            var obj = new
            {
                Id = 1,
                Name = "小明"
            };
            var newObj = obj.DeepClone();
            Assert.IsTrue(obj == newObj);
        }

        [Test]
        public void Test_JObject_JArray()
        {
            //无需验证 JToken 本身有 DeepClone 方法
            var json = new { Id = 1, Name = "小明" }.ToJson();
            var json2 = new[] { new { Id = 1, Name = "小明" } }.ToJson();
        }

        #region RegisterCloneHander
        [Test]
        public void Test_RegisterCloneHander()
        {
            DeepCloneHelper.RegisterCloneHander(typeof(MyClass), (obj, cache) =>
            {
                if (cache.ContainsKey(obj)) return cache.get_Item(obj);
                var res = new MyClass((obj as MyClass).Id);
                cache.set_Item(obj, res);
                return res;
            });
            var cls = new MyClass(1);
            var newCls = cls.DeepClone();
            Assert.IsTrue(cls != newCls);
            newCls.Id.ShouldBe(1);
        }
        class MyClass
        {
            public MyClass(int id)
            {
                Id = id;
            }

            public int Id { get; set; }
        }
        #endregion
    }

    [TestFixture]
    public class ObjectTests_DeepClone2
    {
        #region Poco
        [Test]
        public void Test_ClonePoco()
        {
            var demoRefer = new DemoRefer { Id = 1 };

            var poco = new Poco
            {
                Id = 1,
                Name = "小明",
                ValueTuple = (1, "夏明"),
                ValueTuple2 = (1, "夏明", demoRefer),
                Array = new[] { demoRefer },
                Dictionary = new Dictionary<DemoRefer, DemoRefer> { { demoRefer, demoRefer } },
                HashSet = new HashSet<DemoRefer> { demoRefer },
                JArray = new[] { new { Id = 1, Name = "小明" } }.ToJson().ToObject<JArray>(),
                JObject = new { Id = 1, Name = "小明" }.ToJson().ToObject<JObject>(),
                JToken = new { Id = 1, Name = "小明" }.ToJson().ToObject<JObject>(),
                List = new List<DemoRefer> { demoRefer },
                Tuple = new Tuple<DemoRefer, DemoRefer>(demoRefer, demoRefer),
                LinkedList = new LinkedList<DemoRefer>(new[] { demoRefer, demoRefer }),
                ReadOnlyCollection = new ReadOnlyCollection<DemoRefer>(new[] { demoRefer })
            };
            demoRefer.Poco = poco;

            var newPoco = poco.DeepClone();

            Assert.IsTrue(newPoco != poco);
            newPoco.Id.ShouldBe(1);
            newPoco.Name.ShouldBe("小明");
            //ValueTuple & ValueTuple2
            newPoco.ValueTuple.ShouldBe((1, "夏明"));
            newPoco.ValueTuple2.Item1.ShouldBe(1);
            newPoco.ValueTuple2.Item2.ShouldBe("夏明");
            Assert.IsTrue(newPoco.ValueTuple2.Item3 != poco.ValueTuple2.Item3);
            Assert.IsTrue(newPoco.ValueTuple2.Item3.Id == 1);
            Assert.IsTrue(newPoco.ValueTuple2.Item3.Poco == newPoco);

            //Array
            Assert.IsTrue(newPoco.Array != poco.Array);
            Assert.IsTrue(newPoco.Array[0] == newPoco.ValueTuple2.Item3);

            //Dictionary
            Assert.IsTrue(newPoco.Dictionary != poco.Dictionary);
            Assert.IsTrue(newPoco.Dictionary[newPoco.ValueTuple2.Item3] == newPoco.ValueTuple2.Item3);

            //HashSet
            Assert.IsTrue(newPoco.HashSet != poco.HashSet);
            Assert.IsTrue(newPoco.HashSet.FirstOrDefault() == newPoco.ValueTuple2.Item3);

            //JArray & JObject & JToken
            Assert.IsTrue(newPoco.JArray != poco.JArray);
            Assert.IsTrue(newPoco.JArray.ToJson() == poco.JArray.ToJson());
            Assert.IsTrue(newPoco.JObject != poco.JObject);
            Assert.IsTrue(newPoco.JObject.ToJson() == poco.JObject.ToJson());
            Assert.IsTrue(newPoco.JToken != poco.JToken);
            Assert.IsTrue(newPoco.JToken.ToJson() == poco.JToken.ToJson());

            //List
            Assert.IsTrue(newPoco.List != poco.List);
            Assert.IsTrue(newPoco.List.FirstOrDefault() == newPoco.ValueTuple2.Item3);

            //Tuple
            Assert.IsTrue(newPoco.Tuple != poco.Tuple);
            Assert.IsTrue(newPoco.Tuple.Item1 == newPoco.Tuple.Item2);
            Assert.IsTrue(newPoco.Tuple.Item1 == newPoco.ValueTuple2.Item3);

            //LinkedList
            Assert.IsTrue(newPoco.LinkedList != poco.LinkedList);
            Assert.IsTrue(newPoco.LinkedList.FirstOrDefault() == newPoco.LinkedList.LastOrDefault());
            Assert.IsTrue(newPoco.LinkedList.FirstOrDefault() == newPoco.ValueTuple2.Item3);

            //ReadOnlyCollection
            Assert.IsTrue(newPoco.ReadOnlyCollection != poco.ReadOnlyCollection);
            Assert.IsTrue(newPoco.ReadOnlyCollection.FirstOrDefault() == newPoco.ValueTuple2.Item3);

        }

        class DemoRefer { public int Id { get; set; } public Poco Poco { get; set; } }

        class Poco
        {
            public int Id { get; set; }
            public string Name { get; set; }

            public (int, string) ValueTuple { get; set; }
            public (int, string, DemoRefer) ValueTuple2 { get; set; }

            public Tuple<DemoRefer, DemoRefer> Tuple { get; set; }

            public List<DemoRefer> List { get; set; }
            public DemoRefer[] Array { get; set; }
            public Dictionary<DemoRefer, DemoRefer> Dictionary { get; set; }

            public ReadOnlyCollection<DemoRefer> ReadOnlyCollection { get; set; }
            public HashSet<DemoRefer> HashSet { get; set; }
            public LinkedList<DemoRefer> LinkedList { get; set; }
            public JObject JObject { get; set; }
            public JArray JArray { get; set; }
            public JToken JToken { get; set; }
        }
        #endregion

        class NormalPoco
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
        [Test]
        public void Test_CloneArray()
        {
            var arr = new NormalPoco[] { new NormalPoco { Id = 1, Name = "小明" } };
            var newArr = arr.DeepClone();
            Assert.IsTrue(newArr != arr);
            newArr[0].Id = 2;
            arr[0].Id.ShouldBe(1);

            arr = new[] { arr[0], arr[0] };
            newArr = arr.DeepClone();
            Assert.IsTrue(newArr != arr);
            Assert.IsTrue(newArr[0] == newArr[1]);
        }

        [Test]
        public void Test_CloneIEnumerable()
        {
            IEnumerable<NormalPoco> ienumerable = demo();
            var newienumerable = ienumerable.DeepClone();
            Assert.IsTrue(newienumerable != ienumerable);

            Assert.IsTrue(newienumerable.FirstOrDefault() != ienumerable.FirstOrDefault());
            Assert.IsTrue(newienumerable.LastOrDefault() != ienumerable.LastOrDefault());

            newienumerable.FirstOrDefault().Id.ShouldBe(1);
            newienumerable.FirstOrDefault().Name.ShouldBe("小明");

            newienumerable.LastOrDefault().Id.ShouldBe(2);
            newienumerable.LastOrDefault().Name.ShouldBe("小花");

            IEnumerable<NormalPoco> demo()
            {
                yield return new NormalPoco { Id = 1, Name = "小明" };
                yield return new NormalPoco { Id = 2, Name = "小花" };
            }
        }

        [Test]
        public void Test_CloneList()
        {
            List<NormalPoco> list = new List<NormalPoco> { new NormalPoco { Id = 1, Name = "小明" }, new NormalPoco { Id = 2, Name = "小花" } };
            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);

            Assert.IsTrue(newList.FirstOrDefault() != list.FirstOrDefault());
            Assert.IsTrue(newList.LastOrDefault() != list.LastOrDefault());

            newList.FirstOrDefault().Id.ShouldBe(1);
            newList.FirstOrDefault().Name.ShouldBe("小明");

            newList.LastOrDefault().Id.ShouldBe(2);
            newList.LastOrDefault().Name.ShouldBe("小花");

        }

        [Test]
        public void Test_HashSet()
        {
            HashSet<NormalPoco> list = new HashSet<NormalPoco> { new NormalPoco { Id = 1, Name = "小明" }, new NormalPoco { Id = 2, Name = "小花" } };
            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);

            Assert.IsTrue(newList.FirstOrDefault() != list.FirstOrDefault());
            Assert.IsTrue(newList.LastOrDefault() != list.LastOrDefault());

            newList.FirstOrDefault().Id.ShouldBe(1);
            newList.FirstOrDefault().Name.ShouldBe("小明");

            newList.LastOrDefault().Id.ShouldBe(2);
            newList.LastOrDefault().Name.ShouldBe("小花");
        }

        [Test]
        public void Test_Dictionary()
        {
            Dictionary<int, NormalPoco> list = new Dictionary<int, NormalPoco> { { 1, new NormalPoco { Id = 1, Name = "小明" } }, { 2, new NormalPoco { Id = 2, Name = "小花" } } };
            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);

            Assert.IsTrue(newList[1] != list[1]);
            Assert.IsTrue(newList[2] != list[2]);

            newList[1].Id.ShouldBe(1);
            newList[1].Name.ShouldBe("小明");

            newList[2].Id.ShouldBe(2);
            newList[2].Name.ShouldBe("小花");
        }

        [Test]
        public void Test_ValueTuple()
        {
            (NormalPoco, NormalPoco) tuple = (new NormalPoco { Id = 1, Name = "小明" }, new NormalPoco { Id = 2, Name = "小花" });
            var newTuple = tuple.DeepClone();
            Assert.IsTrue(newTuple != tuple);
            Assert.IsTrue(newTuple.Item1 != tuple.Item1);
            Assert.IsTrue(newTuple.Item2 != tuple.Item2);

            newTuple.Item1.Id.ShouldBe(1);
            newTuple.Item1.Name.ShouldBe("小明");

            newTuple.Item2.Id.ShouldBe(2);
            newTuple.Item2.Name.ShouldBe("小花");
        }

        [Test]
        public void Test_LinkedList()
        {
            LinkedList<NormalPoco> list = new LinkedList<NormalPoco>();
            list.AddLast(new NormalPoco { Id = 1, Name = "小明" });
            list.AddLast(new NormalPoco { Id = 2, Name = "小花" });

            var newList = list.DeepClone();
            Assert.IsTrue(newList != list);
            Assert.IsTrue(newList.FirstOrDefault() != list.FirstOrDefault());
            Assert.IsTrue(newList.LastOrDefault() != list.LastOrDefault());

            newList.FirstOrDefault().Id.ShouldBe(1);
            newList.FirstOrDefault().Name.ShouldBe("小明");
            newList.LastOrDefault().Id.ShouldBe(2);
            newList.LastOrDefault().Name.ShouldBe("小花");
        }

        [Test]
        public void Test_Tuple()
        {
            Tuple<NormalPoco, NormalPoco> tuple = new Tuple<NormalPoco, NormalPoco>(new NormalPoco { Id = 1, Name = "小明" }, new NormalPoco { Id = 2, Name = "小花" });

            var newTuple = tuple.DeepClone();
            Assert.IsTrue(newTuple != tuple);
            Assert.IsTrue(newTuple.Item1 != tuple.Item1);
            Assert.IsTrue(newTuple.Item2 != tuple.Item2);

            newTuple.Item1.Id.ShouldBe(1);
            newTuple.Item1.Name.ShouldBe("小明");

            newTuple.Item2.Id.ShouldBe(2);
            newTuple.Item2.Name.ShouldBe("小花");
        }

        [Test]
        public void Test_ReadOnlyCollection()
        {
            var list = new List<NormalPoco> { new NormalPoco { Id = 1, Name = "小明" }, new NormalPoco { Id = 2, Name = "小花" } };
            ReadOnlyCollection<NormalPoco> readList = new ReadOnlyCollection<NormalPoco>(list);
            var newReadList = readList.DeepClone();

            Assert.IsTrue(newReadList != readList);
            list.Add(new NormalPoco { Id = 3, Name = "小刚" });

            newReadList.Count.ShouldBe(2);
            newReadList[0].Id.ShouldBe(1);
            newReadList[0].Name.ShouldBe("小明");
            newReadList[1].Id.ShouldBe(2);
            newReadList[1].Name.ShouldBe("小花");
        }

        #region CloneStruct
        [Test]
        public void Test_Struct()
        {
            var stru = new MyStruct { Id = 1, Name = "小明", NormalPoco = new NormalPoco { Id = 2, Name = "小花" } };
            var newStru = stru.DeepClone();

            Assert.IsTrue(newStru.NormalPoco != stru.NormalPoco);
            newStru.NormalPoco.Id.ShouldBe(2);
            newStru.NormalPoco.Name.ShouldBe("小花");

        }

        struct MyStruct
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public NormalPoco NormalPoco { get; set; }
        }
        #endregion

        [Test]
        public void Test_Anonymous()
        {
            var obj = new
            {
                Id = 1,
                Name = "小明",
                NormalPoco = new NormalPoco { Id = 2, Name = "小花" }
            };
            var newObj = obj.DeepClone();
            Assert.IsTrue(obj != newObj);
            Assert.IsTrue(obj.NormalPoco != newObj.NormalPoco);

            newObj.NormalPoco.Id.ShouldBe(2);
            newObj.NormalPoco.Name.ShouldBe("小花");
        }

        [Test]
        public void Test_JObject_JArray()
        {
            var json = new { Id = 1, Name = "小明" }.ToJson();
            var json2 = new[] { new { Id = 1, Name = "小明" } }.ToJson();

            var list = new List<JToken>() { json.ToObject<JObject>(), json2.ToObject<JArray>() };
            var newList = list.DeepClone();

            Assert.IsTrue(list != newList);
            Assert.IsTrue(newList[0] != list[0]);
            Assert.IsTrue(newList[1] != list[1]);


            newList[0]["Id"].Value<int>().ShouldBe(1);
            newList[0]["Name"].Value<string>().ShouldBe("小明");

            newList[1][0]["Id"].Value<int>().ShouldBe(1);
            newList[1][0]["Name"].Value<string>().ShouldBe("小明");

        }

        /// <summary>
        /// 测试循环引用
        /// </summary>
        [Test]
        public void CircleTest()
        {
            List<PersonCircle> list = new List<PersonCircle>()
            {
                new PersonCircle()
                {
                    Id=1,
                    Name="小明",
                    Pwd="xiaopming",
                    Book=new BookCircle()
                    {
                        Id=1,
                        Name="语文"
                    }
                },
                new PersonCircle()
                {
                    Id=1,
                    Name="小王",
                    Pwd="wang",
                    Book=new BookCircle()
                    {
                        Id=2,
                        Name="数学"
                    }
                }
            };
            list[0].Book.Person = list[0];
            list[1].Book.Person = list[1];
            var newList = list.DeepClone();

            Assert.IsTrue(list != newList);
            Assert.IsTrue(list[0] != newList[0]);
            Assert.IsTrue(list[0].Book != newList[0].Book);
            Assert.IsTrue(list[1] != newList[1]);
            Assert.IsTrue(list[1].Book != newList[1].Book);

            Assert.IsTrue(newList[0].Book.Person == newList[0]);
            Assert.IsTrue(newList[1].Book.Person == newList[1]);
        }

        #region 循环引用
        public class PersonCircle
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string Pwd { get; set; }
            public BookCircle Book { set; get; }
        }

        public class BookCircle
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public PersonCircle Person { get; set; }
        }
        #endregion

        #region 空参构造函数
        [Test]
        public void NoCtorTest()
        {
            var src = new PersonNoCtor(1, "tom")
            {
                Addr = "road",
                Score = 99
            };
            var dest = src.DeepClone();
            Assert.AreNotEqual(src, dest);
            dest.Id.ShouldBe(1);
            dest.Name.ShouldBe("tom");
            dest.Addr.ShouldBe("road");
            dest.Score.ShouldBe(99);

            var dest2 = src.DeepClone();
            Assert.AreNotEqual(src, dest2);
            Assert.AreNotEqual(dest, dest2);
            dest2.Id.ShouldBe(1);
            dest2.Name.ShouldBe("tom");
            dest2.Addr.ShouldBe("road");
            dest2.Score.ShouldBe(99);
        }
        class PersonNoCtor
        {
            public PersonNoCtor(int id, string Name)
            {
                this.Id = id;
                this.Name = Name;
            }
            public int Id { get; }
            public string Name { get; }
            public string Addr { get; set; }
            public double Score;
        }

        #endregion

        #region 父级属性
        class Parent
        {
            public int Id { get; set; }
            public string Name;
        }
        class Self : Parent
        {
            public int Age { get; set; }
        }

        [Test]
        public void TestParent()
        {
            var src = new Self()
            {
                Id = 1,
                Age = 20,
                Name = "tom"
            };

            var dest = src.DeepClone();
            Assert.AreNotEqual(src, dest);
            dest.Id.ShouldBe(1);
            dest.Name.ShouldBe("tom");
            dest.Age.ShouldBe(20);
        }
        #endregion
    }
}
